﻿Imports System
Imports System.Collections.Generic
Imports System.IO
Imports System.Web
Imports System.Windows
Imports System.Windows.Media
Imports System.Windows.Media.Imaging

Public Class Ckeditor_ImageMedia

    Private _source As BitmapSource
    Private _data As Byte()
    Private _copyright As String

    Public ReadOnly Property Width() As Integer
        Get
            Return _source.PixelWidth
        End Get
    End Property
    Public ReadOnly Property Height() As Integer
        Get
            Return _source.PixelHeight
        End Get
    End Property
    Public ReadOnly Property Copyright() As String
        Get
            Return _copyright
        End Get
    End Property

    ''' <summary>
    ''' Resizes image to fit within the specified with and height, aspect ratio is maintained.
    ''' </summary>
    ''' <param name="width">The maximum width the image has to fit within, set to null to not restrict width.</param>
    ''' <param name="height">The maximum height the image has to fit within, set to null to not restrict height.</param>
    ''' <returns>A reference to this object to allow chaining operations together.</returns>
    Public Function Resize(ByVal width As System.Nullable(Of Integer), ByVal height As System.Nullable(Of Integer)) As Ckeditor_ImageMedia
        If width Is Nothing AndAlso height Is Nothing Then
            Return Me
        End If

        width = If(width, _source.PixelWidth)
        height = If(height, _source.PixelHeight)
        Dim scale As Double = Math.Min(width.Value / CDbl(_source.PixelWidth), height.Value / CDbl(_source.PixelHeight))
        If scale >= 1 Then
            Return Me
        End If

        _source = New TransformedBitmap(_source, New ScaleTransform(scale, scale, 0, 0))
        _data = Nothing
        Return Me
    End Function

    ''' <summary>
    ''' Crops the image in the middle resizing it down to fit as much of the image as possible. Note, image is not enlarged to fit desired width and height.
    ''' </summary>
    ''' <param name="width">The desired width to crop the image to, set to null to not perform horizontal cropping.</param>
    ''' <param name="height">The desired height to crop the image to, set to null to not perform vertical cropping.</param>
    ''' <returns>A reference to this object to allow chaining operations together.</returns>
    Public Function Crop(ByVal width As System.Nullable(Of Integer), ByVal height As System.Nullable(Of Integer)) As Ckeditor_ImageMedia
        If (width Is Nothing AndAlso height Is Nothing) OrElse (width >= _source.PixelWidth AndAlso height >= _source.PixelHeight) Then
            Return Me
        End If

        width = Math.Min(If(width, _source.PixelWidth), _source.PixelWidth)
        height = Math.Min(_source.PixelHeight, (If(height, _source.PixelHeight)))
        Dim scale As Double = Math.Max(width.Value / CDbl(_source.PixelWidth), height.Value / CDbl(_source.PixelHeight))
        If scale < 1 Then
            _source = New TransformedBitmap(_source, New ScaleTransform(scale, scale, 0, 0))
        End If

        Dim marginX As Double = (_source.PixelWidth - width.Value) / 2
        Dim marginY As Double = (_source.PixelHeight - height.Value) / 2

        _source = New CroppedBitmap(_source, New System.Windows.Int32Rect(CInt(Math.Truncate(marginX)), CInt(Math.Truncate(marginY)), width.Value, height.Value))
        _data = Nothing
        Return Me
    End Function

    ''' <summary>
    ''' Returns the binary data of the image in jpg format.
    ''' </summary>
    Public Function ToByteArray() As Byte()
        Return ToByteArray("jpg")
    End Function

    ''' <summary>
    ''' Returns the binary data of the image in the specifed format.
    ''' </summary>
    ''' <param name="format">The format of the bitmap image specified as "gif", "jpg", "png", "bmp", "jxr" or "tiff".</param>
    Public Function ToByteArray(ByVal format As String) As Byte()
        'if image not altered return orig data
        If _data IsNot Nothing Then
            Return _data
        End If

        'placed in a thread set to STA to overcome 0xC0000005 thrown in encoder.Save
        If Not String.IsNullOrEmpty(Copyright) Then
            Dim data() As Byte = Nothing
            Dim worker As New System.Threading.Thread(New System.Threading.ParameterizedThreadStart(Sub(obj As Object)
                                                                                                        Dim encoder As BitmapEncoder = GetEncoder(format)
                                                                                                        Try
                                                                                                            Dim meta As New BitmapMetadata(format)
                                                                                                            meta.Copyright = Copyright
                                                                                                            encoder.Frames.Insert(0, BitmapFrame.Create(_source, Nothing, meta, Nothing))
                                                                                                            Using memoryStream_Renamed As New MemoryStream()
                                                                                                                encoder.Save(memoryStream_Renamed)
                                                                                                                data = memoryStream_Renamed.ToArray()
                                                                                                            End Using
                                                                                                        Catch e1 As NotSupportedException
                                                                                                        End Try
                                                                                                    End Sub))
            worker.SetApartmentState(System.Threading.ApartmentState.STA)
            worker.Start()
            worker.Join()
            If data IsNot Nothing Then
                Return data
            End If
        End If

        Dim targetEncoder As BitmapEncoder = GetEncoder(format)
        targetEncoder.Frames.Add(BitmapFrame.Create(_source))
        Using memoryStream_Renamed As New MemoryStream()
            targetEncoder.Save(memoryStream_Renamed)
            Return memoryStream_Renamed.ToArray()
        End Using
    End Function

    Private Function GetEncoder(ByVal format As String) As BitmapEncoder
        If format = "jpg" Then
            Dim res As New JpegBitmapEncoder()
            res.QualityLevel = 85
            Return res
        End If
        If format = "png" Then
            Return New PngBitmapEncoder()
        End If
        If format = "gif" Then
            Return New GifBitmapEncoder()
        End If
        If format = "tiff" Then
            Return New TiffBitmapEncoder()
        End If
        If format = "jxr" Then
            Return New WmpBitmapEncoder()
        End If
        If format = "bmp" Then
            Return New BmpBitmapEncoder()
        End If
        Throw New Exception("Unrecognised image format: " & format)
    End Function

    ''' <summary>
    ''' Create a image object that can be interacted with from binary data.
    ''' </summary>
    ''' <exception cref="System.NotSupportedException">If the supplied binary data is not a valid image, the NotSupportedException will be thrown.</exception>
    Public Shared Function Create(ByVal data As Byte()) As Ckeditor_ImageMedia
        Dim result As New Ckeditor_ImageMedia
        result._source = BitmapDecoder.Create(New MemoryStream(data), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None).Frames(0)
        result._data = data
        Try
            result._copyright = DirectCast(result._source.Metadata, BitmapMetadata).Copyright
        Catch generatedExceptionName As Exception
        End Try
        Return result
    End Function
End Class